
#ifndef __BMANX_MOVING_HEADER_FILE__
#define __BMANX_MOVING_HEADER_FILE__

#include "body2.h"
#include "../rsp/rsp.h"


////////////////////////////////////////////////////////////////////////////////
class moving : public body
{
public:

    enum {
        cSPEED_BASE                  = 80000,
        cSPEED_DELTA                 = 13000,
        cSPEED_VOLANT                = 320000,
        cSPEED_CRAWL                 = 50000,
        cSPEED_BOMB                  = 120000,
        cSPEED_PUNCH                 = 180000,
        cSPEED_PUNCH_MAN             = 90000,
        cSPEED_AROUND                = 200000,  ///< max speed around corners

        cGRAV_DECCEL                 = 35000,   ///< for normal punch
    };

    static uint SPEED_BASE;
    static uint SPEED_DELTA;
    static uint SPEED_VOLANT;
    static uint SPEED_CRAWL;
    static uint SPEED_BOMB;
    static uint SPEED_PUNCH;
    static uint SPEED_PUNCH_MAN;
    static uint SPEED_AROUND;

    static uint GRAV_DECCEL;

    static void init_constants( uint usec )
    {
        SPEED_BASE     = cSPEED_BASE / usec;
        SPEED_DELTA    = cSPEED_DELTA / usec;
        SPEED_VOLANT   = cSPEED_VOLANT / usec;
        SPEED_CRAWL    = cSPEED_CRAWL / usec;
        SPEED_BOMB     = cSPEED_BOMB / usec;
        SPEED_PUNCH    = cSPEED_PUNCH / usec;
        SPEED_PUNCH_MAN= cSPEED_PUNCH_MAN / usec;
        SPEED_AROUND   = cSPEED_AROUND / usec;

        GRAV_DECCEL    = cGRAV_DECCEL / usec;
    }
    

    static uint get_direction( uint dire, coord* x, coord* y )
    {
        switch( dire & 0xff )
        {
        case dir::EAST      :   *y =        0;  *x =  0x10000;  break;
        case dir::NORTHEAST :   *y =   0xb505;  *x =   0xb505;  break;
        case dir::NORTH     :   *y =  0x10000;  *x =        0;  break;
        case dir::NORTHWEST :   *y =   0xb505;  *x =  -0xb505;  break;
        case dir::WEST      :   *y =        0;  *x = -0x10000;  break;
        case dir::SOUTHWEST :   *y =  -0xb505;  *x =  -0xb505;  break;
        case dir::SOUTH     :   *y = -0x10000;  *x =        0;  break;
        case dir::SOUTHEAST :   *y =  -0xb505;  *x =   0xb505;  break;
        default:                *y = 0; *x = 0;
        }
        return dire & 0xff;
    }

    uint get_direction( coord* x, coord* y ) const              { return get_direction( _xyzd0.d, x, y ); }

    ///Get direction flags
    uint get_direction() const                                  { return _xyzd0.d & 0xff; }
    uint get_full_direction() const                             { return _xyzd0.d; }

    uint get_direction_or_stop() const                          { return is_stopped() ? 0 : (_xyzd0.d & 0xff); }

    uint get_reverse_direction() const
    {
        uint d = _xyzd0.d & 0xff;
        if( d & dir::xSN )  return d ^ dir::xSN;
        if( d & dir::xWE )  return d ^ dir::xWE;
        return 0;
    }

    ///@return s=0, w=1, n=2, e=3, sw=4, nw=5, ne=6, se=7
    static uint get_lookdir( uint dirflg )
    {
        //ewns                  -  e  w   ew n  ne nw 3   s  se sw  3 sn
        static int lookup[] = { 0, 3, 1, -2, 2, 6, 5, -2, 0, 7, 4, -2, -2, -2, -2, -2 };
        return lookup[ dirflg ];
    }

    uint get_lookdir() const                                    { return get_lookdir( get_direction() ); }

    void set_perpendicular_adj( uint curdir, uint totdir )
    {
        uint old = _xyzd0.d;
        if( ((curdir | old) & dir::xSN) == 0  ||  ((curdir | old) & dir::xWE) == 0 )
            return; //not a perpendicular change of direction

        coord c = (old & dir::xSN) ? _xyzd1.y : _xyzd1.x;
        _perpend = (c.val == 0) ? 0 : int_change_sign( c.val, 0x4000 );
/*
        coord c = (old & dir::xSN) ? _xyzd0.y : _xyzd0.x;
        int d = 0x8000 - c.fraction();
        if( d == 0 )  return;

        if( old & (dir::fWEST|dir::fSOUTH) )
        {
            if(d>0x2000)  d -= 0x10000;
        }
        else
        {
            if(d<-0x2000)  d += 0x10000;
        }

        _perpend = d; */
    }

    void set_direction( uint curdir, uint totdir )
    {
        if( !is_stopped()  &&  get_full_direction() == (totdir<<8) + curdir )
            return;

        if( curdir == 0 )  //stop
        {
            _perpend = 0;
            _xyzd1.x = _xyzd1.y = 0;
            _xyzd0.d &= 0xff;
            _flags &= ~fMOVED;
        }
        else
        {
            set_perpendicular_adj(curdir,totdir);
            _xyzd0.d = curdir | (totdir << 8);
            coord fx, fy;
            get_direction( &fx, &fy );
            _xyzd1.x = (fx*_speed)>>8;
            _xyzd1.y = (fy*_speed)>>8;
        }
    }

    void set_orientation( uint curdir )
    {
        _xyzd0.d = curdir;
        
        coord fx, fy;
        get_direction( &fx, &fy );
        if( !is_stopped() ) {
            _xyzd1.x = (fx*_speed)>>8;
            _xyzd1.y = (fy*_speed)>>8;
        }
    }

    void set_direction_temp( uint dir )
    {
        uint sp = _speed;// + (_speed>>1);
        //if( sp > SPEED_AROUND )  sp = SPEED_AROUND;

        coord fx, fy;
        get_direction( dir, &fx, &fy );
        _xyzd1.x = (fx*sp)>>8;
        _xyzd1.y = (fy*sp)>>8;
    }

    void reset_direction_temp()
    {
        coord fx, fy;
        get_direction( &fx, &fy );
        _xyzd1.x = (fx*_speed)>>8;
        _xyzd1.y = (fy*_speed)>>8;
    }

    void change_direction( uint dire, bool press )
    {
        if( in_fire() || !in_world() )  return;

        if(press) //a key press
        {
            uint odir = /*is_stopped() ? 0 : */(get_full_direction()>>8);
            //keep perpendicular direction, if any
            odir &= (dire & dir::xSN)  ?  ~dir::xSN  :  ~dir::xWE;
            odir |= dire;
            set_direction( dire, odir );
        }
        else
        {
            uint odir = get_full_direction() >> 8;
            if( !(odir & dire) )  return;
            odir &= ~dire;
            if(odir)
            {
                uint k = odir;
                uchar i;
                for( i=0; !(k&1); ++i,k>>=1 );
                set_direction( 1<<i, odir );
            }
            else
                set_direction( 0, odir );
        }
    }

    void set_burning()
    {
        _flags |= fIN_FIRE;
    }

    void step_fwd()
    {
        _xyzd0.x += _xyzd1.x;
        _xyzd0.y += _xyzd1.y;
        _xyzd0.z += _xyzd1.z;
    }

    void step_bkw()
    {
        _xyzd0.x -= _xyzd1.x;
        _xyzd0.y -= _xyzd1.y;
        _xyzd0.z -= _xyzd1.z;
    }

    void adjust_misalignment()
    {
//*
        if( _perpend == 0 )  return;

        int sp = _speed<<8;
        sp += sp>>1;
        sp = int_min( sp, (int)int_abs(_perpend) );
        sp = int_change_sign(_perpend,sp);
        _perpend -= sp;

        int d = _xyzd0.d;
        if( d & dir::xSN )
            _xyzd0.x.val += sp;
        else
            _xyzd0.y.val += sp;
/*/
        int d = _xyzd0.d;
        if( d & dir::xSN )
        {
            int dc = _xyzd0.x.fraction() - 0x8000;
            int v = int_min(int_abs(dc), int_abs(_xyzd1.y.val));
            _xyzd0.x -= coord( int_select_by_sign(dc,v,-v) );
        }
        else if( d & dir::xWE )
        {
            int dc = _xyzd0.y.fraction() - 0x8000;
            int v = int_min(int_abs(dc), int_abs(_xyzd1.x.val));
            _xyzd0.y -= coord( int_select_by_sign(dc,v,-v) );
        }
//*/
    }

    void adjust_retraction_misalignment()
    {
        int d = _xyzd0.d;
        if( d & dir::xSN )
        {
            int dc = _xyzd0.x.fraction() - 0x8000;
            if( _perpend>0 && dc>0 )  dc -= 0x10000;
            else if( _perpend<0 && dc<0 )  dc += 0x10000;

            int v = int_min(int_abs(dc), int_abs(_xyzd1.y.val));
            _xyzd0.x -= coord( int_select_by_sign(dc,v,-v) );
        }
        else if( d & dir::xWE )
        {
            int dc = _xyzd0.y.fraction() - 0x8000;
            if( _perpend>0 && dc>0 )  dc -= 0x10000;
            else if( _perpend<0 && dc<0 )  dc += 0x10000;

            int v = int_min(int_abs(dc), int_abs(_xyzd1.x.val));
            _xyzd0.y -= coord( int_select_by_sign(dc,v,-v) );
        }
    }

    ///Check if the movement (step_fwd) was towards the target and return previous distance, 0 otherwise
    coord is_towards( const coord4& dst ) const
    {
        coord qdp = (_xyzd0.x - dst.x).square() + (_xyzd0.y - dst.y).square();
        coord qdn = (_xyzd0.x + _xyzd1.x - dst.x).square() + (_xyzd0.y + _xyzd1.y - dst.y).square();

        return qdp>qdn ? qdp : -qdp;
    }

    void set_pos( uint x, uint y, uint z=0 )
    {
        _xyzd0.x = (x<<16) + (1<<15);
        _xyzd0.y = (y<<16) + (1<<15);
        _xyzd0.z = z;

        //_xyzd1.x = _xyzd1.y = _xyzd1.z = 0;
    }

    ///Get the position one tile further in current direction
    coord4& get_pos_ahead( coord4& dst, coord rad ) const
    {
        int d = _xyzd0.d;
        DASSERT( d!=0 );

        if( d & dir::fEAST )
            dst.x = _xyzd0.x + rad, dst.y = _xyzd0.y;
        else if( d & dir::fWEST )
            dst.x = _xyzd0.x - rad, dst.y = _xyzd0.y;
        else if( d & dir::fNORTH )
            dst.x = _xyzd0.x, dst.y = _xyzd0.y + rad;
        else if( d & dir::fSOUTH )
            dst.x = _xyzd0.x, dst.y = _xyzd0.y - rad;
        return dst;
    }

    ///
    void align()
    {
        _xyzd0.x = _xyzd0.x.trunc() + coord(0x8000);
        _xyzd0.y = _xyzd0.y.trunc() + coord(0x8000);
        _xyzd0.z = 0;
    }

    void align_temp()
    {
        _xyzd0.x = _xyzd0.x.trunc() + coord(0x8000);
        _xyzd0.y = _xyzd0.y.trunc() + coord(0x8000);
        _xyzd0.z = 0;
    }

    void align_to_direction()
    {
        int d = _xyzd0.d;
        if( d & dir::xSN )
            _xyzd0.x = _xyzd0.x.trunc() + coord(0x8000);
        if( d & dir::xWE )
            _xyzd0.y = _xyzd0.y.trunc() + coord(0x8000);
    }


    ///Activate the moving object in the world
    void activate( uint x, uint y )
    {
        coord fx, fy;
        get_direction( &fx, &fy );
        //_xyzd1.x = (fx*_speed)>>8;
        //_xyzd1.y = (fy*_speed)>>8;
        _xyzd1.x = _xyzd1.y = _xyzd1.z = 0;

        set_pos( x, y );
        _flags |= fIN_WORLD;
    }

    ///Activate the moving object in the world
    void activate( bool infire )
    {
        coord fx, fy;
        get_direction( &fx, &fy );
        //_xyzd1.x = (fx*_speed)>>8;
        //_xyzd1.y = (fy*_speed)>>8;
        _xyzd1.x = _xyzd1.y = _xyzd1.z = 0;

        _flags |= fIN_WORLD;
        if(infire)
            _flags |= fIN_FIRE;
    }

    ///Deactivate the moving object
    void deactivate()
    {
        _xyzd1.x = 0;
        _xyzd1.y = 0;

        _flags &= ~xNONPERSISTENT;
        DASSERTX( _nocollide.size() == 0, "should have been untied first" );
    }

    void start()
    {
        coord fx, fy;
        get_direction( &fx, &fy );
        _xyzd1.x = (fx*_speed)>>8;
        _xyzd1.y = (fy*_speed)>>8;
    }

    void stop()
    {
        _xyzd1.x = _xyzd1.y = _xyzd1.z = 0;
        _flags &= ~fMOVED;
    }

    void stop_fall()
    {
        if( _xyzd1.z != 0  ||  _xyzd0.z != 0 )
        {
            _xyzd1.z = _xyzd0.z = 0;
            _xyzd1.x = _xyzd1.y = 0;
            _flags &= ~fMOVED;
        }
    }

    void deccelerate()
    {
        _xyzd1.z -= coord(_xyzd1.d);   //decrease z-speed
    }

    void punch( uint dir, uint tiles, int speed )
    {
        if( in_fire() )  return;

        if(dir)
            _xyzd0.d = dir | (dir << 8);
        coord fx, fy;
        get_direction( &fx, &fy );
        _xyzd1.x = (fx*speed)>>8;
        _xyzd1.y = (fy*speed)>>8;
        // ds=v(t).dt; v(t)=v0-D.t; s(t)=I(v0-D.t)dt=v0.t-D.t^2/2; s=0; v0=D.t/2; t=x/v1; v0=D.x/(2.v1)
        // ds(c)=v(c); v(c)=v0-D.c; s(c)=E(v0-D.c)c; s(c)=v0.c-D.c.(c-1)/2; s=0; v0=D.(c-1)/2; c=x/v1; v0=D.(x/v1-1)/2
        // E(K.x) = (K + 2K + ... + nK) = (n+1)*K*n/2
        int grav = ( GRAV_DECCEL * speed ) / SPEED_PUNCH;
        _xyzd1.z = (tiles*grav<<16)/(2*speed) - (grav<<7);
        _xyzd0.z = 1;
        _xyzd1.d = (int)grav<<8;
    }

    void bounce()
    {
        _xyzd1.z = -_xyzd1.z;     //invert z speed
    }

    bool is_stopped() const         { return _xyzd1.x==0 && _xyzd1.y==0; }
    bool is_man() const             { return (_flags & fISMAN) != 0; }

    bool in_world() const           { return (_flags & fIN_WORLD) != 0; }
    bool in_fire() const            { return (_flags & fIN_FIRE) != 0; }

    uint get_speed() const          { return _speed; }
    void set_speed( int nskate, int ill )
    {
        if( ill>0 )
            _speed = SPEED_VOLANT;
        else if( ill<0 )
            _speed = SPEED_CRAWL;
        else
            _speed = SPEED_BASE + nskate*SPEED_DELTA;

        if( !is_stopped() )
            start();
    }

    moving( bool isman, collisionbody* coll ) : body(coll)
    {
        _flags = isman ? fISMAN : 0;
        _boid._man = 0xff;
        _boid._bombid = 0xff;
        reset();
    }

    void reset()
    {
        //_ntied = 0;
        _flags &= fISMAN|fEXISTS;
        _speed = is_man() ? SPEED_BASE : SPEED_BOMB;
        _xyzd0.x = _xyzd0.y = _xyzd0.z = 0;
        _xyzd0.d = dir::fSOUTH;
        _xyzd1.x = _xyzd1.y = _xyzd1.z = _xyzd1.d = 0;
        _xyzd1.d = 1;

        _perpend = 0;

        //if(state)
        //    touch();
    }



    void set_handsup( bool up )
    {
        if( up && !(_flags & fHANDSUP) ) {
            _flags |= fHANDSUP;
        }
        else if( !up && (_flags & fHANDSUP) ) {
            _flags &= ~fHANDSUP;
        }
    }
    bool get_handsup() const        { return (_flags & fHANDSUP) != 0; }

    void set_jelly( bool j )
    {
        if( j && !(_flags & fJELLY) ) {
            _flags |= fJELLY;
        }
        else if( !j && (_flags & fJELLY) ) {
            _flags &= ~fJELLY;
        }
    }
    bool get_jelly() const          { return (_flags & fJELLY) != 0; }

    uint get_man() const            { return _boid._man; }
    void set_man( uint m, uint b )
    {
        _boid._man = (uchar)m;
        _boid._bombid = b;
    }

    uchar get_unique_bombid() const { return (_boid._bombid << 4) | _boid._man; }

    bool is_tied() const            { return _nocollide.size() > 0; }

    bool is_moving() const          { return (_flags & fMOVED) != 0; }

    bool is_connected() const       { return (_flags & fEXISTS) != 0; }
    void set_connected( bool c )
    {
        if(c)  _flags |= fEXISTS;
        else   _flags &= ~(fEXISTS|fIN_WORLD);
    }


    void nocollide_del( moving* o ) { _nocollide.del_key( o->_boid ); }
    void nocollide_add( moving* o ) { *_nocollide.add() = o->_boid;  _flags &= ~fTIED_OUT; }



    void stream_out( binstream& bin )
    {
        bin.xwrite_raw( &_xyzd0, 2*4*sizeof(coord) );
        bin.xwrite_raw( &_speed, sizeof(_speed)+sizeof(_perpend)+sizeof(_boid)+sizeof(_flags) );
        bin << _nocollide;
    }

    void stream_in( binstream& bin )
    {
        bin.xread_raw( &_xyzd0, 2*4*sizeof(coord) );
        bin.xread_raw( &_speed, sizeof(_speed)+sizeof(_perpend)+sizeof(_boid)+sizeof(_flags) );
        bin >> _nocollide;
    }


    //uint    _ntied;
    //DEFINE_CLUSTER(clu_moving_flags);
    uint    _speed;                 ///< std speed
    int     _perpend;               ///< perpendicular adjustment requirement

    ///Object identifier
    struct boid
    {
        uchar   _man;               ///< owner
        uchar   _bombid;            ///< bomb id, 0xff for player itself

        bool is_man() const         { return _bombid == 0xff; }

        bool operator == (boid o) const  { return *(ushort*)this == *(ushort*)&o; }
        bool operator != (boid o) const  { return *(ushort*)this != *(ushort*)&o; }

        friend binstream& operator << ( binstream& bin, const boid& t )  { return bin << t._man << t._bombid; }
        friend binstream& operator >> ( binstream& bin, boid& t )        { return bin >> t._man >> t._bombid; }
    };

    boid    _boid;                  ///< unique object identifier
    ushort  _flags;                 ///< flags, see the enum below
    
    ///masks for the _flags member
    enum {
        fIN_FIRE                    = 0x0100,
        fIN_WORLD                   = 0x0200,
        fHANDSUP                    = 0x0400,       ///< for men
        fJELLY                      = 0x0800,

        fTIED_OUT                   = 0x0001,       ///< tied man already moving away
        fMOVED                      = 0x0002,       ///< object was moving in the previous frame
        fTRIGGER                    = 0x0004,       ///< triggered bomb
        fNO_COUNTDOWN               = 0x0008,       ///< block the countdown timer (for triggers)

        xNONPERSISTENT              = 0x0fff,

        fISMAN                      = 0x1000,
        fEXISTS                     = 0x8000,       ///< object is connected
    };

    dynarray<boid> _nocollide;       ///< no collide list
};



#endif //__BMANX_MOVING_HEADER_FILE__
